import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';

import s from './AssetVis.module.css';
import HostIpView from './HostIpView';
import IpPortView from './IpPortView';

export default function AssetVis({ className, data, options: customOptions }) {
	const [selectedIp, setSelectedIp] = React.useState(null);
	const [width, setWidth] = React.useState(0);
	const [height, setHeight] = React.useState(0);
	const elemRef = React.useRef(null);

	React.useEffect(() => {
		const { clientWidth, clientHeight } = elemRef.current;

		if (clientWidth && width !== clientWidth) {
			setWidth(Math.max(clientWidth, 700));
		}
		if (clientHeight && height !== clientHeight) {
			setHeight(Math.max(clientHeight, 700));
		}
	}, [elemRef?.current?.clientWidth, elemRef?.current?.clientHeight]);

	const options = React.useMemo(() => {
		const visOptions = {
			...customOptions,
			gapRad: 20,
			hostLabelWidth: 200,
			hostLabelMaxHeight: 20,
			hostLabelMinHeight: 20,
			duration: 1000,
			labelWidthPadding: 10,
			labelHeightPadding: 15,
			xOffset: 20,
		};
		const {
			gapRad,
			hostLabelMaxHeight,
			hostLabelMinHeight,
			hostLabelWidth,
		} = visOptions;
		visOptions.leftStartRad = 180 + gapRad;
		visOptions.leftEndRad = 360 - gapRad;
		visOptions.rightStartRad = gapRad;
		visOptions.rightEndRad = 180 - gapRad;

		let hostLabelHeight = Math.floor(height / data?.hosts?.length);
		if (hostLabelHeight > hostLabelMaxHeight) {
			hostLabelHeight = hostLabelMaxHeight;
		} else if (hostLabelHeight < hostLabelMinHeight) {
			hostLabelHeight = hostLabelMinHeight;
		}
		visOptions.hostLabelHeight = hostLabelHeight;

		visOptions.radius = (Math.min(width, height) - hostLabelWidth) / 2;

		return visOptions;
	}, [customOptions, data, width, height]);

	const visData = React.useMemo(() => {
		const {
			radius,
			hostLabelWidth,
			hostLabelHeight,
			leftStartRad,
			leftEndRad,
			rightStartRad,
			rightEndRad,
			xOffset,
		} = options;

		const hosts = data.hosts.map(host => ({
			type: 'host',
			id: host,
			name: host,
		}));
		const hostMap = hosts.reduce((obj, x) => {
			obj[x.id] = x;
			return obj;
		}, {});

		const hostLabelsHeight = hostLabelHeight * hosts.length;
		const halfHostLabelsHeight = Math.ceil(hostLabelsHeight / 2);
		hosts.forEach((x, i) => {
			x.x = x.rx = hostLabelWidth / -2;
			x.y = x.ry = hostLabelHeight * i - halfHostLabelsHeight;
		});

		const ipMap = Object.entries(data.hostIpMap).reduce((obj, [host, ip]) => {
			if (!obj[ip]) {
				obj[ip] = {
					hosts: new Set([host]),
					type: 'ip',
					id: ip,
					name: ip,
				};
			} else {
				obj[ip].hosts.add(host);
			}

			return obj;
		}, {});
		const ips = Object.values(ipMap);
		const nodeOffset = hostLabelWidth / 2 + xOffset;
		let ipsHalfLen = Math.floor(ips.length / 2);
		ipsHalfLen = ipsHalfLen <= 0 ? 1 : ipsHalfLen;
		const leftSliceRad = (leftEndRad - leftStartRad) / ipsHalfLen;
		ips.slice(ipsHalfLen).forEach((n, idx) => {
			let x = leftEndRad - idx * leftSliceRad;
			const rad = (n.rad = x - 90);
			x = (rad * Math.PI) / 180;
			n.x = Math.cos(x) * radius - nodeOffset;
			n.y = Math.sin(x) * radius;
			n.depth = 1;
			n.position = 'left';
		});
		const rightSliceRad = (rightEndRad - rightStartRad) / ipsHalfLen;
		ips.slice(0, ipsHalfLen).forEach((n, idx) => {
			let x = idx * rightSliceRad + rightStartRad;
			const rad = (n.rad = x - 90);
			x = (rad * Math.PI) / 180;
			n.x = Math.cos(x) * radius + nodeOffset;
			n.y = Math.sin(x) * radius;
			n.depth = 1;
			n.position = 'right';
		});

		const ipNodeLinks = Object.entries(data.hostIpMap).map(([host, ip]) => ({
			source: hostMap[host],
			target: ipMap[ip],
		}));

		return {
			ips,
			ipNodeLinks,
			ipMap,
			hosts,
			hostMap,
			ipPortsMap: data.ipPortsMap,
			hostIpMap: data.hostIpMap,
		};
	}, [data, width, height]);

	let vis;
	if (width && height) {
		vis = selectedIp ? (
			<IpPortView
				width={width}
				height={height}
				options={options}
				ip={visData.ipMap[selectedIp]}
				ports={visData.ipPortsMap[selectedIp]}
				onChangeViewToHostIp={() => setSelectedIp(null)}
			/>
		) : (
			<HostIpView
				width={width}
				height={height}
				options={options}
				data={visData}
				onChangeViewToIpPort={ip => setSelectedIp(ip)}
			/>
		);
	}

	return (
		<div className={cn(className, s.assetVis)} ref={elemRef}>
			<svg width={width} height={height}>
				<g transform={`translate(${width / 2}, ${height / 2})`}>{vis}</g>
			</svg>
		</div>
	);
}

AssetVis.propTypes = {
	className: PropTypes.string,
	data: PropTypes.shape({
		hosts: PropTypes.arrayOf(PropTypes.string).isRequired,
		hostIpMap: PropTypes.object.isRequired,
		ipPortsMap: PropTypes.object.isRequired,
	}),
	options: PropTypes.shape({
		radius: PropTypes.number,
		hostLabelWidth: PropTypes.number,
		hostLabelHeight: PropTypes.number,
		leftStartRad: PropTypes.number,
		leftEndRad: PropTypes.number,
		rightStartRad: PropTypes.number,
		rightEndRad: PropTypes.number,
		hostLabelMaxHeight: PropTypes.number,
		hostLabelMinHeight: PropTypes.number,
		duration: PropTypes.number,
		labelWidthPadding: PropTypes.number,
		labelHeightPadding: PropTypes.number,
		xOffset: PropTypes.number,
	}),
};

AssetVis.defaultProps = {
	className: s.assetVis,
	data: {},
	options: {
		hostLabelWidth: 200,
		hostLabelMaxHeight: 20,
		hostLabelMinHeight: 14,
		duration: 1000,
		labelWidthPadding: 10,
		labelHeightPadding: 15,
		xOffset: 20,
	},
};
